/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.tabbedproperties.composites;

import java.util.Iterator;
import java.util.List;

import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.DefaultToolTip;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ArmEvent;
import org.eclipse.swt.events.ArmListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
import org.eclipse.uml2.uml.ConnectionPointReference;
import org.eclipse.uml2.uml.Pseudostate;
import org.eclipse.uml2.uml.PseudostateKind;
import org.eclipse.uml2.uml.StateMachine;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.commands.MoveInListCommand;
import com.cea.papyrus.core.editor.CommandStackUtils;
import com.cea.papyrus.core.editpart.IUMLElementEditPart;
import com.cea.papyrus.core.ui.dialogs.ConnectionPointReferencePointTreeSelectionDialog;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;

/**
 * Composite that displays the entries/exits of a ConnectionPointReference.
 * 
 * @author David Servat
 * @since 5 apr. 2007
 */
public class ConnectionPointsComposite extends DecoratedTableComposite{

	/**
	 * debug channel.
	 */
	protected static int channel = 501;

	/**
	 * The parent Composite.
	 */
	private Composite parent;

	/**
	 * Constructor with given name for label.
	 */
	public ConnectionPointsComposite(String name){
		super(name);
	}

	/**
	 * key to access UML Data from table items.
	 */ 
	private final static String KEY = "points_data";

	/**
	 * key to access to Pseudostate_entryPoint image.
	 */
	private final static String ENTRY_POINT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"Pseudostate_entryPoint.gif";

	/**
	 * key to access to Pseudostate_exitPoint image.
	 */
	private final static String EXIT_POINT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"Pseudostate_exitPoint.gif";

	/**
	 * The Menu to show all kinds of possible Event.
	 */
	private Menu menu;

	/**
	 * Various menu items.
	 */
	private MenuItem entryItem, exitItem;

	/**
	 * Associated selection listeners.
	 */
	private SelectionListener entryItemListener, exitItemListener;

	/**
	 * Property page
	 */
	private TabbedPropertySheetPage tabbedPropertySheetPage = null;

	/**
	 * Edit part
	 */
	private IUMLElementEditPart editPart = null;

	/**
	 * Stores the tabbed property page before handling the call
	 * to createContent
	 */
	public Composite createContent(Composite parent, TabbedPropertySheetPage tabbedPropertySheetPage){
		this.tabbedPropertySheetPage = tabbedPropertySheetPage;
		return createContent(parent, tabbedPropertySheetPage.getWidgetFactory());
	}

	/**
	 * Enables to store the EditPart for refresh.
	 */
	public void setEditPart(IUMLElementEditPart editPart){
		this.editPart = editPart;
	}

	/**
	 * Creates contents.
	 * 
	 * @param parent Composite.
	 * @param factory TabbedPropertySheetWidgetFactory.
	 * 
	 * @return specific Composite.
	 */
	@Override
	public Composite createContent(Composite parent, TabbedPropertySheetWidgetFactory factory){
		Composite composite = super.createContent(parent, factory);

		//store parent composite
		this.parent = parent;

		//add an additional menu to the add Button
		createMenuForAddButton();

		return composite;
	}



	/**
	 * Adds an additional menu to the addButton. So user can choose which type of trigger to create.
	 */
	private void createMenuForAddButton(){
		menu = new Menu (addButton);

		MenuTooltipListener tooltipListener = new MenuTooltipListener(parent.getShell());
		menu.addMenuListener(tooltipListener);

		// add entry menu item
		entryItem = new MenuItem (menu, SWT.PUSH);
		entryItem.setText("EntryPoint");
		entryItem.setImage(PapyrusPlugin.getImage(ENTRY_POINT_IMAGE));
		entryItem.addSelectionListener(entryItemListener = new AddElementSelectionListener(PseudostateKind.ENTRY_POINT_LITERAL));
		entryItem.addArmListener(tooltipListener);

		// add exit menu item
		exitItem = new MenuItem (menu, SWT.PUSH);
		exitItem.setText("ExitPoint");
		exitItem.setImage(PapyrusPlugin.getImage(EXIT_POINT_IMAGE));
		exitItem.addSelectionListener(exitItemListener = new AddElementSelectionListener(PseudostateKind.EXIT_POINT_LITERAL));
		exitItem.addArmListener(tooltipListener);

		addButton.setMenu (menu);
	}

	/**
	 * Refreshes the composite.
	 */
	@Override
	public void refresh(){
		Iterator it = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(element.getUml2Element() instanceof ConnectionPointReference, "Passed argument is not a ConnectionPointReference");

		ConnectionPointReference ref = (ConnectionPointReference)element.getUml2Element();
		if(ref.getEntries().isEmpty() && ref.getExits().isEmpty()){
			entryItem.setEnabled(true);
			exitItem.setEnabled(true);
			it = ref.getEntries().iterator();
		}
		else if(!ref.getEntries().isEmpty()){
			entryItem.setEnabled(true);
			exitItem.setEnabled(false);
			it = ref.getEntries().iterator();
		}
		else{
			entryItem.setEnabled(false);
			exitItem.setEnabled(true);
			it = ref.getExits().iterator();
		}

		if(!table.isDisposed()){
			//remove parameters from table 
			table.removeAll();

			//read and add point
			while (it.hasNext()){
				Pseudostate ps = (Pseudostate)it.next();
				TableItem item = new TableItem(table, SWT.BORDER);
				item.setData(KEY, ps);
				if(ps != null){
					if(ps.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL)){
						item.setImage(PapyrusPlugin.getImage(ENTRY_POINT_IMAGE));
					} 
					else if(ps.getKind().equals(PseudostateKind.EXIT_POINT_LITERAL)){
						item.setImage(PapyrusPlugin.getImage(EXIT_POINT_IMAGE));
					} 
					item.setText(ps.getQualifiedName());
				}
			}
		}
	}

	/**
	 * Executed behavior when addButton pressed.
	 */
	public void addButtonPressed(){
		menu.setVisible(true);
	}

	/**
	 * Executed behavior when removeButton pressed.
	 */
	public void removeButtonPressed(){
		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(element.getUml2Element() instanceof ConnectionPointReference, "Passed argument is not a ConnectionPointReference");

		//get selection and delete it
		TableItem[] tableItems = table.getSelection();
		for(int i=0; i<tableItems.length; i++){
			//get point
			Pseudostate ps = (Pseudostate)tableItems[i].getData(KEY);
			if(ps.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL))
				((ConnectionPointReference)element.getUml2Element()).getEntries().remove(ps);
			else if(ps.getKind().equals(PseudostateKind.EXIT_POINT_LITERAL))
				((ConnectionPointReference)element.getUml2Element()).getExits().remove(ps);				
		}
	}

	/**
	 * Executed behavior when downButton pressed.
	 */
	public void downButtonPressed(){
		List points = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(element.getUml2Element() instanceof ConnectionPointReference, "Passed argument is not a ConnectionPointReference");

		CompoundCommand compoundCommand = new CompoundCommand();
		TableItem[] tableItems = table.getSelection();

		for(int i=(tableItems.length-1); i>=0; i--){
			//get point
			Pseudostate ps = (Pseudostate)tableItems[i].getData(KEY);
			if(ps.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL))
				points = ((ConnectionPointReference)element.getUml2Element()).getEntries();
			else
				points = ((ConnectionPointReference)element.getUml2Element()).getExits();
			int newIndex = table.indexOf(tableItems[i]) + 1;
			if(newIndex < points.size()){
				compoundCommand.add(new MoveInListCommand(points, ps, newIndex));
			}
		}
		//execute command
		CommandStack stack = CommandStackUtils.getCommandStack();
		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStack");
		stack.execute(compoundCommand);
	}

	/**
	 * Executed behavior when upButton pressed.
	 */
	public void upButtonPressed(){
		List points = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(element.getUml2Element() instanceof ConnectionPointReference, "Passed argument is not a ConnectionPointReference");

		CompoundCommand compoundCommand = new CompoundCommand();
		TableItem[] tableItems = table.getSelection();
		for(int i=0; i<tableItems.length; i++){
			//get point
			Pseudostate ps = (Pseudostate)tableItems[i].getData(KEY);
			if(ps.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL))
				points = ((ConnectionPointReference)element.getUml2Element()).getEntries();
			else
				points = ((ConnectionPointReference)element.getUml2Element()).getExits();
			int newIndex = table.indexOf(tableItems[i]) - 1;
			if(newIndex >= 0){
				compoundCommand.add(new MoveInListCommand(points, ps, newIndex));
			}
		}
		//execute command
		CommandStack stack = CommandStackUtils.getCommandStack();
		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStack");
		stack.execute(compoundCommand);
	}

	/**
	 * Loads a new property page on the effect
	 * 
	 * @param item
	 */
	public void editItem(TableItem item){
		//TODO: find a solution to load a new property page

		Pseudostate ps = (Pseudostate)item.getData(KEY);
		if(ps != null){
			EditPart selection = null;

			Iterator<EditPart> i = LookForElement.getAllOutlineEditParts().iterator();
			while((selection == null) && i.hasNext()){
				EditPart current = i.next();
				if((current.getModel() != null) && current.getModel().equals(ps))
					selection = current ;
			}
			if(selection != null){
				tabbedPropertySheetPage.selectionChanged(getActiveEditor(), new StructuredSelection(selection));
			}
		}
	}

	/**
	 * Disposes of the composite
	 */
	public void dispose(){
		super.dispose();

		if((entryItem != null) && !entryItem.isDisposed())
			entryItem.removeSelectionListener(entryItemListener);
		if((exitItem != null) && !exitItem.isDisposed())
			exitItem.removeSelectionListener(exitItemListener);
	}

	/**
	 * Listener for the menu items.
	 */
	public class AddElementSelectionListener implements SelectionListener{

		/**
		 * Constructor with given kind.
		 * 
		 * @param kind PseudostateKind
		 */
		public AddElementSelectionListener(PseudostateKind kind){
			this.kind = kind;
		}

		/**
		 * Stored PseudostateKind.
		 */
		public PseudostateKind kind;

		/**
		 * Does nothing.
		 * 
		 * @param e SelectionEvent.
		 */
		public void widgetDefaultSelected(SelectionEvent e) {
		}

		/**
		 * What to do when item selected depending on the type of point we want to associate a ConnectionPointReference to.
		 * 
		 * @param e SelectionEvent.
		 */
		public void widgetSelected(SelectionEvent e){
			Debug.debug(this, "create a "+kind.getLiteral()+" associated to a ConnectionPontReference", channel);

			//a compound command to do the job
			CompoundCommand compoundCommand = new CompoundCommand();

			Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
			Assert.isTrue(element.getUml2Element() instanceof ConnectionPointReference, "Passed argument is not a ConnectionPointReference");

			ConnectionPointReference ref = (ConnectionPointReference)element.getUml2Element();

			StateMachine sm = ref.getState().getSubmachine();
			if(sm != null){
				//we want to select an Entry/ExitPoints from the Submachine referenced by the State
				//open a dialog for this
				ConnectionPointReferencePointTreeSelectionDialog dialog = new ConnectionPointReferencePointTreeSelectionDialog(parent.getShell(), sm, kind);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final Pseudostate pseudostate = dialog.getResult(); 
					if(pseudostate != null){
						//a pseudostate was chosen

						if(pseudostate.getKind().equals(PseudostateKind.ENTRY_POINT_LITERAL))
							ref.getEntries().add(pseudostate);
						else if(pseudostate.getKind().equals(PseudostateKind.EXIT_POINT_LITERAL))
							ref.getExits().add(pseudostate);

					}
				}
			}
			//refresh contents
			refresh();
		}
	}
	
	private class MenuTooltipListener implements ArmListener, MenuListener {
		private DefaultToolTip tip;
		private Control control;

		public MenuTooltipListener(Control control) {
			this.control = control;
			tip = new DefaultToolTip(control, ToolTip.RECREATE, true);
		}

		public void widgetArmed(ArmEvent e) {
			MenuItem item = (MenuItem)e.widget;
			if(item.equals(entryItem))
				tip.setText("Associate entry point");
			else if(item.equals(exitItem))
				tip.setText("Associate exit point");
			tip.setPopupDelay(100);
			tip.setHideDelay(3000);
			Point p = control.toControl(control.getDisplay().getCursorLocation());
			p.x += 60;
			if(System.getProperty("os.name").startsWith("Mac OS X"))
				p.y -= 120;
			tip.show(p);
		}

		public void menuHidden(MenuEvent e) {
			tip.hide();
		}

		public void menuShown(MenuEvent e) {

		}
	}

}
